/* Sentinel - IRC Statistical and Operator Services
** jupeserv.c - Jupe service functionality
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: jupeserv.c 7 2010-02-01 19:17:42Z dubkat $
*/

/* Some modifications made by Pantheon <kllrloop@kllrloop.com> */

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "s_string.h"
#include "slog.h"
#include "mem.h"
#include "support.h"

#ifdef JUPESERV

static dlink_list jupe_votes; /* Static is initialized to 0 */

static char errstr[BUFSIZE];

static int j_jupe(struct User *, int, char **);
static int j_nick_jupe(struct User *, int, char **);
static int j_server_jupe(struct User *, int, char **);
static int j_unjupe(struct User *, int, char **);
static int j_nick_unjupe(struct User *, int, char **);
static int j_server_unjupe(struct User *, int, char **);
static int j_pending(struct User *, int, char **);
static int j_help(struct User *, int, char **);
static int j_cping(struct User *, int, char **);
static int j_cversion(struct User *, int, char **);

static struct MsgTable jupe_table[] =
  {
    { "HELP", 0, 0, NULL, j_help },
    { "JUPE", 0, 0, NULL, j_jupe },
    { "UNJUPE", 0, 0, NULL, j_unjupe },
    { "PENDING", 0, 0, NULL, j_pending },
    { "\001PING", 0, 0, NULL, j_cping },
    { "\001VERSION\001", 0, 0, NULL, j_cversion },
    { (char *)0, 0, 0, NULL, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { "JUPE", "help/jupeserv/jupe", 0 },
    { "UNJUPE", "help/jupeserv/unjupe", 0 },
    { "PENDING", "help/jupeserv/pending", 0 },
    { (char *)0, (char *)0, 0 }
  };

void jupeserv(struct User *u, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;
  static char buf[BUFSIZE];

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  strlcpy(buf, param, BUFSIZE);

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (!parc)
    return ;

  if (parv[0][0] != '\001')
    slog(DEFAULT_LOG, L_INFO, "%s received message from %s: %s", JupeServ.nick,
         u->nick, buf);

  /* XXX - Oper only all the time */
  if (!IsOper(u))
  {
    if (parv[0][0] != '\001')
    {
      notice(u->nick, JupeServ.nick,
              "This service is only available to IRCops.");
      return ;
    }
  }

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  for (i = 0; jupe_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], jupe_table[i].name))
    {
      status = jupe_table[i].handler(u, parc, parv);
      if (status == 1)
        slog(DEFAULT_LOG, L_ERR, "Error with jupeserv command %s: %s",
             parv[0], (errstr) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }
  /* If we get here, we did not get a valid command...
  ** Make sure not to say anything to the user if it was a 
  ** CTCP that was not matched
  */
  if (*parv[0] == '\001')
    return ;
  notice(u->nick, JupeServ.nick, "Invalid command: %s", parv[0]);
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int j_help(struct User *u, int parc, char *parv[])
{
  int i;

  spy("%s:  HELP Request by %s for %s", JupeServ.nick, u->nick,
      (parv[1]) ? parv[1] : "NO QUERY");
  sendto_dcc_flag(CF_COMMANDS, "%s:  HELP Request by %s for %s",
                  JupeServ.nick, u->nick, (parv[1]) ? parv[1] : "NO QUERY");
  if (parc < 2)
  {
    send_help_file(u->nick, JupeServ.nick, "help/jupeserv/index", "index");
    if (u->myuser != NULL)
      send_help_file(u->nick, JupeServ.nick, "help/jupeserv/aindex", "aindex");
    return 0;
  }

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      if (help_table[i].myuonly)
      {
        if (u->myuser)
          send_help_file(u->nick, JupeServ.nick, help_table[i].filename,
                         help_table[i].name);
        else
          notice(u->nick, JupeServ.nick, "Permission Denied.");
      }
      else
      {
        send_help_file(u->nick, JupeServ.nick, help_table[i].filename,
                       help_table[i].name);
      }

      return 0;
    }
  }

  notice(u->nick, JupeServ.nick, "Unknown Help Topic: \2%s\2", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int j_pending(struct User *u, int parc, char *parv[])
{
  dlink_node *dl;
  int count = 0;
  struct JupeVote *jv;

  if (!IsAllOperJupe() && (!u->myuser || !IsSetOperJupe(u->myuser)))
  {
    slog(DEFAULT_LOG, L_ERR, "Access denied (PENDING) to %s", u->nick);
    notice(u->nick, JupeServ.nick, "Access Denied.");
    spy("%s: PENDING Attempt by %s (failed)", JupeServ.nick, u->nick);
    sendto_dcc_flag(CF_JUPE, "%s: PENDING Attempt by %s (failed)",
                    JupeServ.nick, u->nick);
    return 0;
  }

  if (settings.jupevotes == 1)
  {
    notice(u->nick, JupeServ.nick, "Jupe voting disabled.");
    return 0;
  }

  DLINK_FOREACH(dl, jupe_votes.head)
  {
    jv = dl->data;
    count++;

    notice(u->nick, JupeServ.nick, "%d) %s by %s on %s [%s]",
            count, jv->server, jv->oper, jv->oper_serv, sftime(jv->ts));
  }

  notice(u->nick, JupeServ.nick, "End of list.");
  return 0;
}

static int j_jupe(struct User *u, int parc, char *parv[])
{
  if (!IsAllOperJupe() && (!u->myuser || !IsSetOperJupe(u->myuser)))
  {
    slog(DEFAULT_LOG, L_ERR, "Access denied (JUPE) to %s", u->nick);
    notice(u->nick, JupeServ.nick, "Access Denied.");
    spy("%s: JUPE %s Attempt by %s (failed)", JupeServ.nick,
        (parv[1]) ? parv[1] : "<nothing>", u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: JUPE %s Attempt by %s (failed)", JupeServ.nick,
                    (parv[1]) ? parv[1] : "<nothing>", u->nick);
    return 0;
  }

  if (parc < 2)
  {
    notice(u->nick, JupeServ.nick, "Syntax:  /msg %s JUPE <server> <reason>",
           JupeServ.nick);
    spy("%s: JUPE Attempt by %s (failed) (no parameter)",
        JupeServ.nick, u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: JUPE Attempt by %s (failed) (no parameter)",
                    JupeServ.nick, u->nick);
    strcpy(errstr, "Missing parameter in JUPE");
    return 1;
  }

  if (strchr(parv[1], '.') == NULL)
    return j_nick_jupe(u, parc, parv);
  else
    return j_server_jupe(u, parc, parv);
}

static int j_server_jupe(struct User *u, int parc, char *parv[])
{
  struct Jupe *j;
  char reason[LINE];

  j = FindServerJupe(parv[1]);

  if (j)
  {
    notice(u->nick, JupeServ.nick, "Cannot jupe already juped server");
    spy("%s: JUPE %s Attempt by %s (already juped)", JupeServ.nick,
        parv[1], u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: JUPE Attempt by %s (failed) (no parameter)",
                    JupeServ.nick, u->nick);
    /* Not a code error */
    return 0;
  }

  if (strcasecmp(me.uplink->name, parv[1]) == 0)
  {
    notice(u->nick, JupeServ.nick, "Cannot jupe your uplink");
    spy("%s: JUPE %s Attempt by %s (uplink)", JupeServ.nick,
        parv[1], u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: JUPE %s Attempt by %s (uplink)", JupeServ.nick,
                    parv[1], u->nick);
    /* Not a code error */
    return 0;
  }

  if (strcasecmp(settings.name, parv[1]) == 0)
  {
    notice(u->nick, JupeServ.nick, "Cannot jupe myself");
    spy("%s: JUPE %s Attempt by %s (myself)", JupeServ.nick,
        parv[1], u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: JUPE %s Attempt by %s (myself)", JupeServ.nick,
                    parv[1], u->nick);
    /* Not a code error */
    return 0;
  }

  reason[0] = '\0';
  ExpandParv(reason, LINE, 2, parc, parv);
  if (reason[0] == '\0')
  {
    strcpy(reason, "No Reason");
  }

  /* Jupe voting */
  if (settings.jupevotes > 1)
  {
    dlink_node *dl, *tdl;
    int current_votes = 0;
    struct JupeVote *jv;

    DLINK_FOREACH(dl, jupe_votes.head)
    {
      jv = dl->data;

      /* Handle masking? */
      if (strcasecmp(parv[1], jv->server) == 0)
      {
        /* This is a 2nd (or more) vote */

        /* Make sure this server hasn't voted previously */
        if (strcasecmp(u->server->name, jv->oper_serv) == 0)
        {
          notice(u->nick, JupeServ.nick,
                  "Server %s already voted [oper %s]", jv->oper_serv,
                  jv->oper);
          return 0;
        }
        current_votes++;
      }
    }

    if ((current_votes + 1) < settings.jupevotes)
    {
      /* This is just a vote, not ready to trigger. */
      jv = smalloc(sizeof(struct JupeVote));
      dl = dlink_create();
      memcounts.jupevote++;
      strlcpy(jv->server, parv[1], MAXHOST);
      strlcpy(jv->oper, u->nick, MAXNICK + 1);
      strlcpy(jv->oper_serv, u->server->name, MAXHOST);
      jv->ts = CurrentTime;
      strlcpy(jv->reason, reason, LINE);

      dlink_add(jv, dl, &jupe_votes);

      wallops(settings.name,
              "%s!%s@%s voting (%d votes) for JUPE for %s - Reason: %s",
              u->nick, u->username, u->hostname, current_votes + 1,
              parv[1], reason);
      alert("%s: JUPE VOTE(%d) %s by %s: %s", JupeServ.nick,
            current_votes + 1, parv[1], u->nick, reason);
      sendto_dcc_flag(CF_JUPE, "%s: JUPE VOTE(%d) %s by %s: %s",
                      JupeServ.nick, current_votes + 1, parv[1], u->nick,
                      reason);
      slog(DEFAULT_LOG, L_NOTICE, "%s: JUPE VOTE(%d) %s by %s: %s",
           JupeServ.nick, current_votes + 1, parv[1], u->nick,
           reason);
      return 0;
    }
    else
    {
      char first_reason[LINE];
      char nicklist[LINE];

      strlcpy(nicklist, u->nick, LINE);

      /* Triggering the jupe */
      DLINK_FOREACH_SAFE(dl, tdl, jupe_votes.head)
      {
        jv = dl->data;

        if (strcasecmp(jv->server, parv[1]) == 0)
        {
          dlink_delete(dl, &jupe_votes);

          /* Preserve the last seen (oldest, as dlink_add adds new fields to
          ** the head of the list) reason, as we want the original reason
          ** to jupe the service with
          */
          /* strcpy can be used since first_reason and jv->reason are the
          ** same size
          */
          strcpy(first_reason, jv->reason);
          strlcat(nicklist, ",", LINE);
          strlcat(nicklist, jv->oper, LINE);

          free(jv);
          dlink_free(dl);
          memcounts.jupevote--;
        }
      }
      AddServerJupe(parv[1], nicklist, CurrentTime, first_reason, 0);
      wallops(settings.name, "Opers %s adding JUPE for %s - Reason: %s",
              nicklist, parv[1], first_reason);
      notice(u->nick, JupeServ.nick, "Jupe %s added, enforced", parv[1]);
      alert("%s: JUPE %s by %s: %s", JupeServ.nick, parv[1], nicklist,
            first_reason);
      sendto_dcc_flag(CF_JUPE, "%s: JUPE %s by %s: %s", JupeServ.nick,
                      parv[1], nicklist, first_reason);
      slog(DEFAULT_LOG, L_NOTICE, "%s: JUPE %s by %s: %s", JupeServ.nick,
           parv[1], nicklist, first_reason);
      SaveJupes();

      return 0;
    }
  }

  /* Not using jupe voting, pass through */

  /* Wallops routine changed to issue nick!identd@host for other IRCops
  ** to see in wallops - Pantheon 10-15-02
  */

  AddServerJupe(parv[1], u->myuser->username, CurrentTime, reason, 0);
  wallops(settings.name, "%s!%s@%s adding JUPE for %s - Reason: %s",
          u->nick, u->username, u->hostname, parv[1], reason);
  notice(u->nick, JupeServ.nick, "Jupe %s added, enforced", parv[1]);
  alert("%s: JUPE %s by %s: %s", JupeServ.nick, parv[1], u->nick, reason);
  sendto_dcc_flag(CF_JUPE, "%s: JUPE %s by %s: %s", JupeServ.nick,
                  parv[1], u->nick, reason);
  slog(DEFAULT_LOG, L_NOTICE, "%s: JUPE %s by %s: %s", JupeServ.nick,
       parv[1], u->nick, reason);
  SaveJupes();

  return 0;
}

static int j_nick_jupe(struct User *u, int parc, char *parv[])
{
  struct Jupe *j;
  char reason[LINE];

  j = FindNickJupe(parv[1]);

  if (j)
  {
    notice(u->nick, JupeServ.nick, "Cannot jupe already juped server");
    spy("%s: JUPE %s Attempt by %s (already juped)", JupeServ.nick,
        parv[1], u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: JUPE Attempt by %s (failed) (no parameter)",
                    JupeServ.nick, u->nick);
    /* Not a code error */
    return 0;
  }

  reason[0] = '\0';
  ExpandParv(reason, LINE, 2, parc, parv);
  if (reason[0] == '\0')
  {
    strcpy(reason, "No Reason");
  }

  /* Wallops routine changed to issue nick!identd@host for other IRCops
  ** to see in wallops - Pantheon 10-15-02
  */

  AddNickJupe(parv[1], u->myuser->username, CurrentTime, reason);
  wallops(settings.name, "%s!%s@%s adding JUPE for %s - Reason: %s",
          u->nick, u->username, u->hostname, parv[1], reason);
  notice(u->nick, JupeServ.nick, "Jupe %s added, enforced", parv[1]);
  alert("%s: JUPE %s by %s: %s", JupeServ.nick, parv[1], u->nick, reason);
  sendto_dcc_flag(CF_JUPE, "%s: JUPE %s by %s: %s", JupeServ.nick,
                  parv[1], u->nick, reason);
  slog(DEFAULT_LOG, L_NOTICE, "%s: JUPE %s by %s: %s", JupeServ.nick,
       parv[1], u->nick, reason);
  SaveJupes();

  return 0;
}

static int j_unjupe(struct User *u, int parc, char *parv[])
{
  if (!IsAllOperJupe() && (!u->myuser || !IsSetOperJupe(u->myuser)))
  {
    slog(DEFAULT_LOG, L_ERR, "Access denied (UNJUPE) to %s", u->nick);
    spy("%s: UNJUPE %s Attempt by %s (failed)", JupeServ.nick,
        (parv[1]) ? parv[1] : "<nothing>", u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: UNJUPE %s Attempt by %s (failed)", JupeServ.nick,
                    (parv[1]) ? parv[1] : "<nothing>", u->nick);
    notice(u->nick, JupeServ.nick, "Access Denied.");
    return 0;
  }

  if (parc < 2)
  {
    notice(u->nick, JupeServ.nick, "Syntax:  /msg %s UNJUPE <server>",
           JupeServ.nick);
    spy("%s: UNJUPE Attempt by %s (failed) (no parameter)",
        JupeServ.nick, u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: UNJUPE Attempt by %s (failed) (no parameter)",
                    JupeServ.nick, u->nick);
    strcpy(errstr, "Missing parameter in UNJUPE");
    return 1;
  }

  if (strchr(parv[1], '.') == NULL)
    return j_nick_unjupe(u, parc, parv);
  else
    return j_server_unjupe(u, parc, parv);
}

static int j_server_unjupe(struct User *u, int parc, char *parv[])
{
  struct Jupe *j;

  j = FindServerJupe(parv[1]);

  if (!j)
  {
    notice(u->nick, JupeServ.nick, "Jupe %s does not exist", parv[1]);
    spy("%s: UNJUPE %s Attempt by %s (jupe does not exist)",
        JupeServ.nick, parv[1], u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: UNJUPE %s Attempt by %s (jupe does not exist)",
                    JupeServ.nick, parv[1], u->nick);
    /* Not a code error */
    return 0;
  }

  DelServerJupe(parv[1]);
  wallops(settings.name, "%s!%s@%s removing JUPE for %s", u->nick,
          u->username, u->hostname, parv[1]);
  alert("%s: UNJUPE %s by %s", JupeServ.nick, parv[1], u->nick);
  sendto_dcc_flag(CF_JUPE, "%s: UNJUPE %s by %s", JupeServ.nick, parv[1],
                  u->nick);
  notice(u->nick, JupeServ.nick, "Jupe %s removed", parv[1]);
  slog(DEFAULT_LOG, L_NOTICE, "%s: UNJUPE %s by %s", JupeServ.nick, parv[1],
       u->nick);
  SaveJupes();

  return 0;
}

static int j_nick_unjupe(struct User *u, int parc, char *parv[])
{
  struct Jupe *j;

  j = FindNickJupe(parv[1]);

  if (!j)
  {
    notice(u->nick, JupeServ.nick, "Jupe %s does not exist", parv[1]);
    spy("%s: UNJUPE %s Attempt by %s (jupe does not exist)",
        JupeServ.nick, parv[1], u->nick);
    sendto_dcc_flag(CF_JUPE,
                    "%s: UNJUPE %s Attempt by %s (jupe does not exist)",
                    JupeServ.nick, parv[1], u->nick);
    /* Not a code error */
    return 0;
  }

  DelNickJupe(parv[1]);
  wallops(settings.name, "%s!%s@%s removing JUPE for %s", u->nick,
          u->username, u->hostname, parv[1]);
  alert("%s: UNJUPE %s by %s", JupeServ.nick, parv[1], u->nick);
  sendto_dcc_flag(CF_JUPE, "%s: UNJUPE %s by %s", JupeServ.nick, parv[1],
                  u->nick);
  notice(u->nick, JupeServ.nick, "Jupe %s removed", parv[1]);
  slog(DEFAULT_LOG, L_NOTICE, "%s: UNJUPE %s by %s", JupeServ.nick, parv[1],
       u->nick);
  SaveJupes();

  return 0;
}

static int j_cping(struct User *u, int parc, char *parv[])
{
  char command[BUFSIZE];

  ExpandParv(command, BUFSIZE, 1, parc, parv);
  if (command[0] == '\0')
    return 1;
  sts(":%s NOTICE %s :\001PING %s", JupeServ.nick, u->nick, command);
  slog(DEFAULT_LOG, L_INFO, "CTCP PING from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int j_cversion(struct User *u, int parc, char *parv[])
{
  sts(":%s NOTICE %s :\001VERSION %s %s -> %s\001",
      JupeServ.nick, u->nick, stats_version, VersionFlags(), stats_date);
  slog(DEFAULT_LOG, L_INFO, "CTCP VERSION from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

void expire_pending_jupe_votes(void)
{
  dlink_node *dl, *tdl;
  struct JupeVote *jv;

  DLINK_FOREACH_SAFE(dl, tdl, jupe_votes.head)
  {
    jv = dl->data;

    if (CurrentTime >= (jv->ts + JUPE_EXP_TIME))
    {
      dlink_delete(dl, &jupe_votes);
      memcounts.jupevote--;
      free(jv);
      dlink_free(dl);
    }
  }
}

#endif
